package com.theeviljames.complexANN;

import java.util.ArrayList;
import java.util.Arrays;

import com.theeviljames.exceptions.ComplexANNException;
import com.theeviljames.pure.Complex;
import com.theeviljames.pure.ComplexMatrixOps;
import com.theeviljames.pure.IComplexMatrixOps;

public class ComplexANN {

	private ComplexANNLayer[] ann;
	private int[] topology;
	private static final IComplexMatrixOps c = ComplexMatrixOps.getComplexMatrixOps();
	private ArrayList<Complex> deltaHistory = new ArrayList<Complex>();
	
	
	public ComplexANN(int[] topology, boolean linear, Complex learnRate) {
		this.topology = topology;
		ann = new ComplexANNLayer[topology.length-1];
		for(int i = 0; i < topology.length-1; i++){
			ann[i] = new ComplexANNLayer(topology[i], topology[i+1], linear, learnRate);
		}
	}

	public void train(int epochs, Complex[][][] inputs, Complex[][][] targets) throws ComplexANNException{
		if(inputs.length!=targets.length)throw new ComplexANNException("From ComplexANN.train() >There must be the same number of inputs as targets");
		deltaHistory.clear();
		for(int i = 0; i < epochs; i++){
			Complex sumOfDelta = new Complex(0,0);
			for(int j = 0; j < inputs.length; j++){
				Complex[][] outputs = getOutputs(inputs[j]);
				Complex[][] deltaError = c.minus(outputs,targets[j]);
				sumOfDelta = sumOfDelta.add(sumOfSquaresError(deltaError));
				backprop(deltaError);
			}
			deltaHistory.add(sumOfDelta);
		}
		System.out.println("Following is the delta error history");
		for(Complex c:deltaHistory)System.out.println(c);
	}
	
	public Complex[][] getOutputs(Complex[][] inputs) throws ComplexANNException{
		Complex[][] result = inputs;
		for(int i = 0; i < ann.length; i++){
			result = ann[i].getOutputs(result);
		}
		return result;
	}
	
	public void backprop(Complex[][] deltaError) throws ComplexANNException{
		for(int i = ann.length-1; i >= 0; i--){
			deltaError = ann[i].backprop(deltaError);
		}
	}
	
	public Complex sumOfSquaresError(Complex[][] delta){
		//This will be the (where d = delta error) d'd
		Complex result = c.times(c.transpose(delta),delta)[0][0];
		return result;
	}
	
	public void print(){
		System.out.println("The topology of the Complex ANN is >\n" + Arrays.toString(topology) + "\n");
		for(int i = 0; i < ann.length; i++){
			System.out.println("Layer " + (i+1));
			ann[i].print();
		}
	}
	
	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		try{
/*			ComplexANN ann = new ComplexANN(new int[]{2,2}, true, new Complex(0.05,0.05));
			ann.print();
			
			Complex[][] inputs = new Complex[][]{{new Complex(1,2)},{new Complex(1,2)}};
			System.out.println("Inputs\n");
			c.print(inputs);
			
			Complex[][] outputs = ann.getOutputs(inputs);
			System.out.println("Outputs\n");
			c.print(outputs);
*/		
			ComplexANN ann = new ComplexANN(new int[]{2,2},true, new Complex(0.1,0.1));
			ann.print();
			Complex[][][] inputs  = new Complex[][][]{
					{{new Complex(0,0)},{new Complex(0,0)}},	
					{{new Complex(1,0)},{new Complex(0,0)}},	
					{{new Complex(0,0)},{new Complex(0,1)}},	
					{{new Complex(1,0)},{new Complex(0,1)}},	
			};
			Complex[][][] targets  = new Complex[][][]{
					{{new Complex(0,0)},{new Complex(0,0)}},	
					{{new Complex(1,0)},{new Complex(0,1)}},	
					{{new Complex(1,0)},{new Complex(0,1)}},	
					{{new Complex(0,0)},{new Complex(0,0)}},			};
			
			ann.train(100,inputs,targets);
		}
		catch(Exception e){
			e.printStackTrace();
		}
	}

}
